iT邦幫忙

2022 iThome 鐵人賽

DAY 30
0

從ClojureScript, Reagent到re-frame(3) data loop & 完賽感言

大家早安!今天是完賽日囉(握拳)

最近都一直下雨,先來一張re-frame文件裡美美的圖

就像是水循環一樣,水經過了evaporation蒸發變成水蒸氣、經過凝結、gravity重力降下雨與雪、也經過convection對流而循環不已。我們可以把前端世界的的data flow資料流也做一樣的想像。

re-frame這個框架是屬於event driven design,會透過event來趨動data走完六個階段的生命週期。這些event就像骨牌一樣,第一個階段完成後,產生第二個階段,第二個階段完成產生下個階段...。最終在前端SPA頁面快速反饋網站使用者需要的功能與效果。

we hang functions on the (data) loop at various points to compute the data's phase changes. Ref

既然是框架,必定有很完整的架構,讓我們在撰寫前端cljs程式時,可以把資料流不同階段的function拆分,掛在data loop的相對應的階段。

以下就要介紹在re-frame前端框架裡的data loop六階段:

    1. Event Dispatch(事件發送)
    1. Event Handler (事件處理)
    1. Effect Handler (Side-effect處理)
    1. Query (query出更新的state)
    1. View (state透過Function運算轉為View)
    1. DOM (re-frame 還給reagent -> 還給react)

想知道從 Event DispatchEvent Handling Effect Handling Query 到subscribe state產生 view 到前端的DOM這一系列的code怎麼實作出來的思考脈絡,可以看文件這篇詳細的說明

1. Event Dispatch(事件發送)

當事件被trigger的時候,是data loop循環的第一個階段Event Dispatch

eg.

  • 使用者刪除按鈕
  • bot收到message

這個第一階段最重要,如果沒有第一個階段,後面其他五個階段也不會產生啦。

使用者刪除某個按鈕的event function,可能長得像這樣:

(defn delete-button 
  [item-id]
  [:div.garbage-bin 
    :on-click #(re-frame.core/dispatch [:delete-item item-id])])

2. Event handling(事件處理)

為了回應event,app就要決定下一步action是什麼

在Event handling階段的函式會去做計算這個event會改變了外部的哪些值(side-effect)

More accurately, event handler functions compute a declarative description of the required side effects - represented as data.

eg. 使用者按下刪除按鈕,會去db 裡刪資料

(defn handler                          
 [coeffects event]               
 (let [item-id  (second event)   ;; extract id from event vector
       db       (:db coeffects)  ;; extract the current application state
       new-db   (dissoc-in db [:items item-id])]   ;; new app state
   {:db new-db}))                ;; a map of the necessary effects
                                 ;; `new-db` is the newly computed application state

以上的語法可以用destructing(解構)再寫的更簡潔一些

(defn handler
  [{:keys [db]} [_ item-id]]    ;; <--- new: obtain db and item-id directly
  {:db  (dissoc-in db [:items item-id])})    ;; 

3. (Side)Effect handling

side effect會改變外部的值這句聽起來很嚇人,但是如果沒有side effect的話。網頁停在那裡,不做任何動作,不改變任何state,也不更新任何值,不是很無用的嗎XD。

上個階段Event handling階段計算好的side effect,在這個階段會執行。

這個階段改變app-state( or app-db),就會triger第4到6的階段。

在re-frame程式裡,會把Day27介紹的reagent/atom binding到app-db

語法:

(def app-db  (reagent/atom {}))    ;; a Reagent atom, containing a map

atom的概念雖然是透過reference方式來管理狀態變更,但我們可以想像成app-db、或in-memory db,這樣就更為直接啦~

re-frame的機制因為有了effect handling階段,會讓side effect更容易被monitor和debug。

re-frame app可以用reg-fx註冊effects handlers,例如這樣:

(re-frame.core/reg-fx       ;; part of the re-frame API
  :db                       ;; the effects key 
  (fn [val]                 ;; the handler function for the effect
    (reset! app-db val)))   ;; put the new value into the ratom app-db

但在實務上不需要去註冊app-db這個步驟

我們反而是需要寫自己的handler然後註冊,
例如註冊一個刪除事件

(re-frame.core/reg-event-fx   ;; a part of the re-frame API
  :delete-item                ;; the kind of event
  handler)                          ;; the handler function for this kind of event

延伸: react的state

由於re-frame是奠基在react框架上,因此關於state與lifecycle也可以參考react官方文件 - State and Lifecycle的說明。

State is similar to props, but it is private and fully controlled by the component.


re-frame 從event dispatch到操作reagent / react DOM圖解說明

ref

re-frame第四到第六的階段用以下方程式來表達

v = f(s)
;; view = function(state)

當state改變時,function會重新運算,render對應的view

4. Query

上個階段update完app-db之後,會trigger第四階段的query把app state的資料拿出來。帶入ViewFunction (v = f(s)公式裡的f),快速地運算出前端需要render的view。

(defn query-fn
  [db v]         ;; db is the current value in app-db, v the query vector
  (:items db))   ;; not much of a materialised view

(re-frame.core/reg-sub  ;; part of the re-frame API
   :query-items         ;; query id  
   query-fn)            ;; function to perform the query 

5. View

這個階段的ViewFunction code可能長得像這樣子:

(defn items-view
  []
  (let [items  (subscribe [:query-items])]  ;; source items from app state
    [:div (map item-render @items)]))   ;; assume item-render already written

其中的 (subscribe [:query-items]) 就會在上個階段call query-n

多個ViewFunction組合起來就會是component啦~我們在第25天有實做過幾個簡單的Reagent component

這些ViewFunction會運算並回傳24天介紹的hiccup (一種用cljs語法表達的前端html格式) 再往下一階段(DOM)傳。

6. DOM

Document Object Model,文件物件模型,把HTML文件內的各個標籤,包括文字、圖片等等都定義成物件

為了要render出正確的DOM ,re-frame使用的機制是subscribe來訂閱最新的state,確保UI可以一直更新並即時顯示。

當hiccup格式轉為DOM,這裡就是已經是react及reagent(用cljs寫react)的範疇啦~

從以上的說明,是不是對re-frame是怎麼再把reagent包一層的概念有更多的認識呢?

完賽感言

很榮幸參與今年2022年的鐵人賽,這三十天內除了鐵人賽也跟前兩次(2018, 2020)一樣在鐵人賽的過程中去跑了馬拉松,很幸運地兩者都有兼顧順利完賽。而且跟前兩次經驗不一樣的是,今年寫鐵人賽時的身份已經是工程師啦!

之前也蠻懷疑全職的時候有辦法連續撐30天、每天下班後都打起精神再持續花2-4小時學習新東西嗎?事實證明,人的潛力是無窮的。 "Whether you think you can, or you think you can't, you're right" 。相信自己的毅力一定可以做得到,那就做得到!百分之百的commitment,一點點的事前規劃和策略運用、加上一點點時間管理大師的能力。完賽並不是遙不可及的事。(假日也是有抽空去旅行、跑步、爬山等,只是出外有時要記得帶電腦去飯店趕稿。:P)

第一次寫Functional programming相關系列文,在構思及撰寫過程有很多不完美的地方,在寫的時候力求初淺理解但仍然有誤解之處,會再抽空反覆回去修改文章內容。十分感謝公司前輩的指點及參與討論~

謝謝推坑我組團的龍哥,謝謝自主學習的團員們互相激勵,謝謝在30天為我加油打氣的好同事們、Bater、親友們和黃色小鴨鴨(XD) Love you all。

未來展望

培養新的習慣需要21天,經過30天的洗禮之後。每天都會早起研究程式,很喜歡這種morning routine,是種很充實、很踏實的感覺。

但這30天只是functional programming學習的起點~希望自己能再更熟悉FP思維,會繼續深化研究下去,活用Clojure語法來解Kata

鐵人賽我們下次見!


上一篇
[Day29] 從ClojureScript, Reagent 到 ref-rame (2) form & Event Handler Tracing 簡介
系列文
後端Developer實戰ClojureScript: Reagent與前端框架 Reframe30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
Bater
iT邦新手 4 級 ‧ 2022-10-14 08:43:13

恭喜完賽!哇~又一次克服了連續三十天的挑戰,而且是全新主題,同時間還有正職工作與馬拉松,真的是很不簡單。

我相信這種克服挑戰的精神與能力會是未來面對困境珍貴的技能,可以屢屢化險為夷,然後得到更多成長與體驗。

很榮幸能成為見證這個艱難的道路上的支持者與鼓勵者,不論之後會遇到什麼樣的新課題,也會陪伴你一起克服的唷!
/images/emoticon/emoticon42.gif

謝謝Bater在我成為工程師的路上一直不斷鼓勵我,未來也要一起在專業領域上精進加油唷!/images/emoticon/emoticon51.gif

Bater iT邦新手 4 級 ‧ 2022-11-16 16:12:11 檢舉

/images/emoticon/emoticon51.gif

我要留言

立即登入留言